﻿using Consul;
using DeZhen.Core.Consul.Entity;
using DeZhen.Core.Framework.Business.Ioc;
using DeZhen.Core.Framework.Entity;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DeZhen.Core.Consul.Platform
{
    [Service]
    public class ConsulPlatform : IConsulPlatform
    {
        [Autowired]
        private IConsulClient _consulClient;
        [Value("Consul")]
        private ConsulConfig _config;

        private ConcurrentDictionary<string, int> ServerCalls = new ConcurrentDictionary<string, int>();
        public ConsulPlatform(IAutowiredPlatform autowiredService)
        {
            autowiredService.Autowired(this);
        }
        public async Task<string> GetServerAsync(string serviceName)
        {
            var service = await GetServerInfoAsync(serviceName);
            return $"{service.Address}:{service.Port}";
        }
        public async Task<AgentService> GetServerInfoAsync(string serviceName)
        {
            var services = await GetServerListAsync(serviceName);
            return BalancingRoute(services, serviceName);
        }
        public async Task<IList<AgentService>> GetServerListAsync(string serviceName)
        {
            var result = await _consulClient.Health.Service(serviceName);

            if (result.StatusCode != System.Net.HttpStatusCode.OK)
            {
                throw new ConsulRequestException("获取服务信息失败！", result.StatusCode);
            }

            return result.Response.Select(s => s.Service).ToList();
        }


        private AgentService BalancingRoute(IList<AgentService> services, string key)
        {
            if (services == null || !services.Any())
            {
                throw new ArgumentNullException(nameof(services), $"当前未找到{key}可用服务！");
            }

            switch (_config.LBStrategy)
            {
                case "First":
                    return First(services);
                case "Random":
                    return Random(services);
                case "RoundRobin":
                    return RoundRobin(services, key);
                case "WeightRandom":
                    return WeightRandom(services);
                case "WeightRoundRobin":
                    return WeightRoundRobin(services, key);
                default:
                    return RoundRobin(services, key);
            }
        }
        private AgentService First(IList<AgentService> services)
        {
            return services.First();
        }
        private AgentService Random(IList<AgentService> services)
        {
            return services.ElementAt(new Random().Next(0, services.Count()));
        }
        private AgentService RoundRobin(IList<AgentService> services, string key)
        {
            var count = ServerCalls.GetOrAdd(key, 0);
            var service = services.ElementAt(count++ % services.Count());
            ServerCalls[key] = count;
            return service;
        }
        public AgentService WeightRandom(IList<AgentService> services)
        {
            var pairs = services.SelectMany(s =>
            {
                var weight = 1;
                if (s.Meta.ContainsKey("Weight") && int.TryParse(s.Meta["Weight"], out int w))
                {
                    weight = w;
                }
                var result = new List<AgentService>();
                for (int i = 0; i < weight; i++)
                {
                    result.Add(s);
                }
                return result;
            }).ToList();
            return Random(pairs);
        }
        public AgentService WeightRoundRobin(IList<AgentService> services, string key)
        {
            var pairs = services.SelectMany(s =>
            {
                var weight = 1;
                if (s.Meta.ContainsKey("Weight") && int.TryParse(s.Meta["Weight"], out int w))
                {
                    weight = w;
                }
                var result = new List<AgentService>();
                for (int i = 0; i < weight; i++)
                {
                    result.Add(s);
                }
                return result;
            }).ToList();
            return RoundRobin(pairs, key);
        }
    }
}
